from pathlib import Path

import pandas as pd
from requests import RequestException

from utils.logger import create_logger

logger = create_logger(__name__)

PROJECT_ROOT = Path(__file__).parent.parent

def process_pressure_data(df1, df2, type_column='type', time_column="TIME"):
    """
    处理压力数据（筛选间隔1小时的点+多nodename平均）
    1. 按时间排序后，将时间转为整点，每间隔1小时取一个数据点
    2. 同一station+时间+压力类型对应多nodename时，求压力平均值
    3. 输出列：场站编号、进口压力、出口压力、时间（不含nodename）
    """
    # 1. 检查第一个表格的必要列
    required_columns = ['nodename', 'station', type_column]
    missing_columns = [col for col in required_columns if col not in df1.columns]
    if missing_columns:
        raise ValueError(f"第一个表格缺少必要列：{', '.join(missing_columns)}")

    # 2. 预处理df1：过滤无效nodename + 构建映射字典
    # 定义无效nodename判断函数（排除空值、NaN、纯空格）
    def is_valid_nodename(nodename):
        if pd.isna(nodename):  # 排除NaN、pd.NA等缺失值
            return False
        if isinstance(nodename, str) and nodename.strip() == "":  # 排除空字符串、纯空格
            return False
        return True

    # 过滤无效nodename行
    df1_valid = df1[df1['nodename'].apply(is_valid_nodename)].copy()
    total_nodename = len(df1)
    valid_nodename_count = len(df1_valid)
    invalid_count = total_nodename - valid_nodename_count

    # 打印过滤信息，辅助调试
    print(f"\n映射表节点校验：")
    print(f"总节点数：{total_nodename}")
    print(f"过滤无效节点数（空值/NaN/纯空格）：{invalid_count}")
    print(f"有效节点数：{valid_nodename_count}")

    # 若无可用于匹配的有效节点，直接终止
    if valid_nodename_count == 0:
        raise ValueError("映射表中无有效nodename（全部为空值/NaN/纯空格），无法进行压力数据匹配")

    # 构建有效nodename→(station, 压力类型)的映射字典（去除首尾空格，避免匹配误差）
    nodename_map = {}
    for _, row in df1_valid.iterrows():
        # 强制转为字符串并去除首尾空格（处理" A "→"A"这类情况）
        nodename = str(row['nodename']).strip()
        pressure_type = row[type_column]  # 1=进站，2=出站
        nodename_map[nodename] = (row['station'], pressure_type)

    print(f"已构建{len(nodename_map)}个有效节点的映射关系")

    # 3. 处理第二个表格：筛选间隔1小时的数据点
    print(f"\n正在解析{time_column}列并筛选间隔1小时的数据点...")

    # 3.1 打印TIME列前5个实际值，帮助排查格式问题
    print(f"\nTIME列实际数据格式（前5条）：")
    for i, val in enumerate(df2[time_column].head(5)):
        print(f"  第{i+1}条：{val}（数据类型：{type(val)}）")

    # 3.2 自动识别时间格式（兼容横杠/斜杠、单/双数字月/日、有无秒数等）
    df2['time_parsed'] = pd.to_datetime(
        df2[time_column],
        errors='coerce'  # 解析失败的设为NaN，不报错
    )

    # 3.3 过滤时间解析失败的行，并按时间排序
    df2_valid = df2[df2['time_parsed'].notna()].copy()
    if len(df2_valid) == 0:
        # 若仍全部失败，打印更多示例并报错
        print(f"\nTIME列所有数据均解析失败！完整示例（前10条）：")
        for i, val in enumerate(df2[time_column].head(10)):
            print(f"  第{i+1}条：{val}")
        raise ValueError("所有时间均解析失败，请检查TIME列数据格式（支持常见格式：2025/3/4、2025-03-04、2025/3/4 15:00、2025-03-04 15:00:00等）")

    # 新增：将时间转换为整点（将分钟、秒、微秒设为0）
    df2_valid['time_hourly'] = df2_valid['time_parsed'].dt.floor('h')
    print("已将时间标准化为整点")

    df2_sorted = df2_valid.sort_values(by='time_hourly').reset_index(drop=True)
    print(f"第二个表格原始数据行数：{len(df2)}")
    print(f"时间解析有效行数：{len(df2_sorted)}")

    # 3.3 筛选间隔1小时的数据点（基于整点时间）
    if len(df2_sorted) == 0:
        raise ValueError("无有效时间数据可筛选")

    # 初始化筛选结果：先取第一条数据的整点时间
    hourly_data = [df2_sorted.iloc[0]]
    last_selected_hour = df2_sorted.iloc[0]['time_hourly']  # 上一个选中的整点时间

    # 遍历排序后的时间，每间隔1小时取一个点
    for _, row in df2_sorted.iloc[1:].iterrows():
        current_hour = row['time_hourly']
        # 计算与上一个选中整点时间的间隔（单位：小时）
        time_diff_hours = (current_hour - last_selected_hour).total_seconds() / 3600

        # 若间隔≥1小时，选中当前数据，并更新上一个选中时间
        if time_diff_hours >= 1:
            hourly_data.append(row)
            last_selected_hour = current_hour

    # 转为DataFrame（筛选后的间隔1小时数据）
    df2_hourly = pd.DataFrame(hourly_data)
    print(f"筛选后间隔1小时的数据行数：{len(df2_hourly)}")

    if len(df2_hourly) == 0:
        raise ValueError("未筛选到间隔1小时的数据点，请检查时间序列是否足够长")

    # 4. 收集所有符合条件的压力数据（列表存储，避免循环concat）
    result_list = []
    # 获取所有压力列（结尾为_p），用于后续调试提示
    pressure_cols = [col for col in df2_hourly.columns if col.endswith('_p')]

    for _, df2_row in df2_hourly.iterrows():
        # 输出时间格式统一为 年/月/日 时:分:秒（与需求一致）
        current_time_str = df2_row['time_hourly'].strftime("%Y/%m/%d %H:%M:%S")

        # 遍历所有有效nodename，匹配对应的压力列（nodename_p）
        for nodename, (station, pressure_type) in nodename_map.items():
            target_col = f"{nodename}_p"  # 目标列名（nodename+_p）
            # 检查列是否存在，且压力值非空
            if target_col in df2_hourly.columns and pd.notna(df2_row[target_col]):
                result_list.append({
                    '场站编号': station,  # 此处将station改为场站编号
                    '压力类型': '进口压力' if pressure_type == 1 else '出口压力',  # 调整压力类型名称
                    '压力值': df2_row[target_col],
                    '时间': current_time_str
                })

    # 5. 检查是否匹配到压力数据
    if not result_list:
        # 打印压力列和nodename示例，帮助调试匹配问题
        print(f"\n第二个表格的压力列（结尾为_p）：{pressure_cols[:10]}...")
        print(f"有效nodename列表（前10个）：{list(nodename_map.keys())[:10]}...")
        raise ValueError("未匹配到压力数据！请确认nodename + _p 与压力列名一致（如nodename=A→列名=A_p）")

    # 6. 按场站编号+时间+压力类型分组，计算平均值（多nodename合并）
    intermediate_df = pd.DataFrame(result_list)
    grouped_df = intermediate_df.groupby(
        ['场站编号', '时间', '压力类型'],  # 按场站编号、筛选后的时间、压力类型分组
        as_index=False
    )['压力值'].mean()  # 多nodename对应同一类型压力时求平均

    # 7. 重塑表格：将“压力类型”转为“进口压力”“出口压力”列
    result_df = grouped_df.pivot(
        index=['场站编号', '时间'],  # 行：场站编号+时间
        columns='压力类型',  # 列：压力类型（进口/出口）
        values='压力值'  # 值：平均压力值
    ).reset_index()

    # 8. 补全缺失列（确保进口/出口压力列始终存在）
    if '进口压力' not in result_df.columns:
        result_df['进口压力'] = None
    if '出口压力' not in result_df.columns:
        result_df['出口压力'] = None

    # 9. 调整列顺序并排序（按场站编号+时间）
    # 自动识别时间格式排序，无需指定固定格式
    result_df['时间_sort'] = pd.to_datetime(result_df['时间'])
    result_df = result_df.sort_values(by=['场站编号', '时间_sort']).drop(columns='时间_sort')
    result_df = result_df.reset_index(drop=True)

    return result_df


# 将时间转换为整点并转为时间戳（自动适配输入格式）
def convert_to_timestamp(dt_str):
    # 自动识别时间格式，无需固定格式
    dt = pd.to_datetime(dt_str)
    hour_start = dt.replace(minute=0, second=0, microsecond=0)
    return int(hour_start.timestamp())


if __name__ == "__main__":
    # 配置文件路径（保持你的实际路径）
    file1_path = PROJECT_ROOT / "data" / "reflect_files" / "station_pressure_reflection-new.xlsx"
    file2_path = PROJECT_ROOT / "data" / "dummy_data" / "NO_predictions.csv"
    output_file = PROJECT_ROOT / "pressure.xlsx"
    time_column = "TIME"  # 在外部定义时间列名，确保打印时可访问

    try:
        # 1. 读取原始文件
        logger.info("正在读取文件...")
        df1 = pd.read_excel(file1_path, engine="openpyxl", dtype={'station': str})
        logger.info(f"df1:{df1}")
        df2 = pd.read_csv(file2_path)

        # 2. 打印关键信息（修复：使用外部定义的time_column）
        print(f"\n第一个表格（映射表）列名：{df1.columns.tolist()}")
        print(f"第二个表格（压力数据）时间列名：{time_column}")

        # 3. 核心处理：调用函数（传入time_column参数）
        result_df = process_pressure_data(
            df1, df2,
            type_column='压力对比type值',
            time_column=time_column  # 将时间列名传入函数
        )

        # 4. 输出结果（预览+保存）
        print("\n=== 处理后结果预览（前10行） ===")
        print(result_df.head(10))
        print(f"\n处理后总数据行数：{len(result_df)}")

        # 保存到Excel（时间格式为 2025/3/4 15:00:00）
        result_df.to_excel(output_file, index=False, engine="openpyxl")
        print(f"\n结果已保存到：{output_file}")

        # 获取列名列表并按新列名重命名（适配接口字段）
        df_renamed = result_df.rename(columns={
            '场站编号': 'stationId',
            '进口压力': 'inputPress',
            '出口压力': 'outputPress',
            '时间': 'time'
        })
        # 将json数据的时间转化为时间戳（整点）
        df_renamed['time'] = df_renamed['time'].apply(convert_to_timestamp)
        # 按照行来转换为JSON
        json_records = df_renamed.to_json(orient="records")
        print("\nJSON格式数据（前500字符）：", json_records[:500])

    except FileNotFoundError as e:
        print(f"\n错误：文件不存在 → {e}")
    except ValueError as e:
        print(f"\n错误：数据处理异常 → {e}")
    except RequestException as e:
        print(f"接口请求异常 → 原因：{e}")
    except TimeoutError as e:
        print(f"接口请求超时 → 原因：{e}")
    except Exception as e:
        print(f"\n其他错误 → 原因：{str(e)[:200]}")